package org.enhance.mybatis.util;

import lombok.extern.slf4j.Slf4j;
import org.enhance.common.util.Detect;
import org.enhance.common.util.ExceptionUtil;
import org.enhance.spring.util.SpringUtil;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

@Slf4j
public class DbLock {
    private final String lockKey;
    private final boolean releaseKey;
    private final JdbcTemplate jdbcTemplate;

    private Connection conn;
    private Boolean connAutoCommit;
    private PreparedStatement preparedStatement;

    private DbLock(String key, boolean releaseKey) {
        this.lockKey = key;
        this.releaseKey = releaseKey;
        this.jdbcTemplate = SpringUtil.getBean(JdbcTemplate.class);
        long count = this.jdbcTemplate.queryForObject(String.format("SELECT count(1) FROM distributed_lock where lock_name = '%s'", lockKey), long.class);
        if (!Detect.isPositive(count)) {
            try {
                int successCount = jdbcTemplate.update(String.format("INSERT INTO distributed_lock (`lock_name`) VALUES ('%s');", lockKey));
                log.info("add lock_name:{} success.{}", lockKey, successCount);
            } catch (Exception e) {
                log.info("add lock_name:{} failed.error:{}", lockKey, ExceptionUtil.toMessage(e, true));
            }
        }
    }

    public static DbLock create(String key) {
        return create(key, true);
    }

    public static DbLock create(String key, boolean releaseKey) {
        return new DbLock(key, releaseKey);
    }

    public void lock() {
        try {
            doLock(false);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public boolean lockWithNowait() {
        try {
            doLock(true);
            return true;
        } catch (SQLException e) {
            log.debug(e.getMessage(), e);
            return false;
        }
    }

    private void doLock(boolean nowait) throws SQLException {
        conn = this.jdbcTemplate.getDataSource().getConnection();
        connAutoCommit = conn.getAutoCommit();
        conn.setAutoCommit(false);

        String lockSql = String.format("select * from distributed_lock where lock_name = '%s' for update", lockKey);
        if (nowait) {
            lockSql = String.format("select * from distributed_lock where lock_name = '%s' for update nowait", lockKey);
        }
        preparedStatement = conn.prepareStatement(lockSql);
        preparedStatement.execute();
    }

    public void unlock() {
        // commit
        if (conn != null) {
            try {
                conn.commit();
            } catch (SQLException e) {
                log.error(e.getMessage(), e);
            }
            try {
                conn.setAutoCommit(connAutoCommit);
            } catch (SQLException e) {
                log.error(e.getMessage(), e);
            }
            try {
                conn.close();
            } catch (SQLException e) {
                log.error(e.getMessage(), e);
            }
        }
        // close PreparedStatement
        if (null != preparedStatement) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                log.error(e.getMessage(), e);
            }
        }
        if (releaseKey) {
            try {
                jdbcTemplate.update(String.format("DELETE FROM distributed_lock WHERE lock_name = '%s'", lockKey));
            } catch (Exception e) {
                log.error("release lockKey:{} failed.error:{}", lockKey, ExceptionUtil.toMessage(e, true));
            }
        }
    }
}
